Android View 的软件绘制

10/30/2024

# 1. 了解软件绘制与硬件加速绘制

绘制(或者叫渲染)就是把代码中的描述一块区域如何显示的代码/指令软换为一块可以用于显示设备显示的内存 Buffer 的过程。

View 的绘制共分为两种:硬件加速绘制和软件绘制:

  1. 硬件加速绘制是借助能高效处理图形计算的 GPU 来完成。
  2. 软件绘制使用 CPU 调用 libSkia 库来进行绘制

从 Android 4.0 开始,系统默认开启硬件加速。我们如果在 App 的 AndroidManifest 里面,在 Application 标签里面加上

android:hardwareAccelerated="false"
1

就可以关闭硬件绘制,使用软件绘制。

代码层面,硬件绘制的复杂度远超软件绘制,柿子挑软的捏,我们先学习软件绘制。

# 2. skia 库的基本使用

软件绘制会使用 skia 库绘制图形,在分析 View 的软件绘制流程之前,有必要先了解一下 skia 库的基本使用。

使用上,skia 库还是比较简单的:

  • SKBitmap 用来存储图形数据
SkBitmap bitmap = new SkBitmap();
//设置位图格式及宽高
bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
//分配位图所占空间
bitmap->allocPixels();
1
2
3
4
5
  • SKCanvas 封装了所有画图操作的函数,通过调用这些函数,我们就能实现绘制操作。
//使用前传入bitmap
SkCanvas canvas(bitmap);
//移位,缩放,旋转,变形操作
translate(SkiaScalar dx, SkiaScalar dy);
scale(SkScalar sx, SkScalar sy);
rotate(SkScalar degrees);
skew(SkScalar sx, SkScalar sy);
//绘制操作
drawARGB(u8 a, u8 r, u8 g, u8 b....)  //给定透明度以及红,绿,兰3色,填充整个可绘制区域。
drawColor(SkColor color...) //给定颜色color, 填充整个绘制区域。
drawPaint(SkPaint& paint) //用指定的画笔填充整个区域。
drawPoint(...)//根据各种不同参数绘制不同的点。
drawLine(x0, y0, x1, y1, paint) //画线,起点(x0, y0), 终点(x1, y1), 使用paint作为画笔。
drawRect(rect, paint) //画矩形,矩形大小由rect指定,画笔由paint指定。
drawRectCoords(left, top, right, bottom, paint),//给定4个边界画矩阵。
drawOval(SkRect& oval, SkPaint& paint) //画椭圆,椭圆大小由oval矩形指定。
//……其他操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • Skpaint 用来设置绘制内容的风格,样式,颜色等信息
    //定义画笔
    SkPaint paint1, paint2, paint3;

    paint1.setAntiAlias(true);
    paint1.setColor(SkColorSetRGB(255, 0, 0));
    paint1.setStyle(SkPaint::kFill_Style);

    paint2.setAntiAlias(true);
    paint2.setColor(SkColorSetRGB(0, 136, 0));
    paint2.setStyle(SkPaint::kStroke_Style);
    paint2.setStrokeWidth(SkIntToScalar(3));

    paint3.setAntiAlias(true);
    paint3.setColor(SkColorSetRGB(136, 136, 136));

    sk_sp<SkTextBlob> blob1 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.0f, 0.0f));
    sk_sp<SkTextBlob> blob2 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.5f, 0.0f));

    canvas->clear(SK_ColorWHITE);
    canvas->drawTextBlob(blob1.get(), 20.0f, 64.0f, paint1);
    canvas->drawTextBlob(blob1.get(), 20.0f, 144.0f, paint2);
    canvas->drawTextBlob(blob2.get(), 20.0f, 224.0f, paint3);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3. View 绘制的发起

View 绘制的发起是在 ViewRootImpl#draw 方法中:

    // ViewRootImpl

    public final Surface mSurface = new Surface();

    private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
        
        Surface surface = mSurface;
        
        // ......

        // mDirty 的计算过程?
        final Rect dirty = mDirty;
        
        // .......

        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            
            if (isHardwareEnabled()) {  // 硬件绘制
               
                // ......
                // 硬件绘制
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
                
                // ......
                
                // 软件绘制
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }

        // .......

        return useAsyncReport;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

默认情况下 isHardwareEnabled() 方法返回 true,走硬件绘制路线,做了单独配置则会走软件绘制路线。

# 4. 软件绘制整体流程

软件绘制 drawSoftware 方法的实现如下:

// ViewRootImpl

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

        // Draw with software renderer.
        final Canvas canvas;

        try {
            // 关注点1
            // 获取画布对象 Canvas
            canvas = mSurface.lockCanvas(dirty);
            // 密度
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(mTag, "Could not lock surface", e);
            // Don't assume this is due to out of memory, it could be
            // something else, and if it is something else then we could
            // kill stuff (or ourself) for no reason.
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                        + canvas.getWidth() + ", h=" + canvas.getHeight() + ", dirty: " + dirty
                        + ", xOff=" + xoff + ", yOff=" + yoff);
                //canvas.drawARGB(255, 255, 0, 0);
            }

            // If this bitmap's format includes an alpha channel, we
            // need to clear it before drawing so that the child will
            // properly re-composite its drawing on a transparent
            // background. This automatically respects the clip/dirty region
            // or
            // If we are applying an offset, we need to clear the area
            // where the offset doesn't appear to avoid having garbage
            // left in the blank areas.
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }

            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            if (DEBUG_DRAW) {
                Context cxt = mView.getContext();
                Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                        ", metrics=" + cxt.getResources().getDisplayMetrics() +
                        ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
            }

            // 画布是否需要移动
            canvas.translate(-xoff, -yoff);
            if (mTranslator != null) {
                mTranslator.translateCanvas(canvas);
            }
            canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);

            // 关注点2
            // root view 绘制
            mView.draw(canvas);

            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
            try {
                // 关注点3
                // 提交绘制的内容到 Surface
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
  • 关注点1,获取 Canvas 对象,Canvas 可以理解为一个画布,会和一块 Buffer 相关联
  • 关注点2,View 树使用 Canvas 对象进行绘制
  • 关注点3,提交绘制好的 Buffer 去显示

接下来,我们详细分析这三点。

# 5. Canvas 对象的初始化

Surface#lockCanvas 方法会准备一个画布对象返回,给后续的 draw 过程使用。其实现如下:

// /frameworks/base/core/java/android/view/Surface.java

    // 指向 Native 层 BBQSurface 对象
    long mNativeObject;

    // 重要成员
    private final Canvas mCanvas = new CompatibleCanvas();

    public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject != 0) {
                // Ideally, nativeLockCanvas() would throw in this situation and prevent the
                // double-lock, but that won't happen if mNativeObject was updated.  We can't
                // abandon the old mLockedObject because it might still be in use, so instead
                // we just refuse to re-lock the Surface.
                throw new IllegalArgumentException("Surface was already locked");
            }
            // 关注点
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

这里会接着调用 nativeLockCanvas 方法:

// /frameworks/base/core/java/android/view/Surface.java
    private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
            throws OutOfResourcesException;
1
2
3
  • 第一个参数传入的是 Surface 的成员 mNativeObject,是一个 native 指针,指向 Native 层 BBQSurface 对象(这个在窗口的添加过程做分析,目前知道即可)。
  • 第二个参数 Canvas 来自 Surface 的 mCanvas 成员,是用于绘制的画布
  • 第三个参数 Rect dirty,表示需要绘制的区域。

nativeLockCanvas 对应的 JNI 实现:

// /frameworks/base/core/jni/android_view_Surface.cpp
 
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    
    // 取到对应的 Native 层 Surface 对象,实际类型是 BBQSurface
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!isSurfaceValid(surface)) {
        jniThrowException(env, IllegalArgumentException, NULL);
        return 0;
    }

    if (!ACanvas_isSupportedPixelFormat(ANativeWindow_getFormat(surface.get()))) {
        native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
    }

    // 用于保存 Java 层传递过来的 Dirty 区域
    Rect dirtyRect(Rect::EMPTY_RECT);
    Rect* dirtyRectPtr = NULL;

    // 拿到 Java 层传递过来的 dirty 区域
    if (dirtyRectObj) {
        dirtyRect.left   = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirtyRect.top    = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirtyRect.right  = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        dirtyRectPtr = &dirtyRect;
    }

    ANativeWindow_Buffer buffer;

    // 关注点1,获取 buffer
    status_t err = surface->lock(&buffer, dirtyRectPtr);

    if (err < 0) {
        const char* const exception = (err == NO_MEMORY) ?
                OutOfResourcesException : IllegalArgumentException;
        jniThrowException(env, exception, NULL);
        return 0;
    }

    // 关注点2
    // 利用 java 层 Canvas 对象构建一个新的 `graphics::Canvas` 对象
    graphics::Canvas canvas(env, canvasObj);

    // 关注点3
    // buffer 与 graphics::Canvas 关联
    canvas.setBuffer(&buffer, static_cast<int32_t>(surface->getBuffersDataSpace()));

    if (dirtyRectPtr) {
        canvas.clipRect({dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom});
    }

    if (dirtyRectObj) {
        env->SetIntField(dirtyRectObj, gRectClassInfo.left,   dirtyRect.left);
        env->SetIntField(dirtyRectObj, gRectClassInfo.top,    dirtyRect.top);
        env->SetIntField(dirtyRectObj, gRectClassInfo.right,  dirtyRect.right);
        env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
    }

    // Create another reference to the surface and return it.  This reference
    // should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
    // because the latter could be replaced while the surface is locked.

    // 返回 Surface
    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
  • 关注点1,调用 surface::lock 申请一块图形 buffer,lock 的过程在 BBQ 章节已经分析过 native 的代码了,这里不是重点。
  • 关注点2,利用 java 层 Canvas 对象构建一个新的 graphics::Canvas 对象
  • 关注点3,把 buffer 与 graphics::Canvas 对象关联起来

类相互关系比较复杂,这里先给一个整体的类图。

20241211120626

接下来对照类图,分析本节的三个关注点:

  • 5.1 申请图形 Buffer
  • 5.2 构建 graphics::Canvas 对象
  • 5.3 把 buffer 与 graphics::Canvas 对象关联起来

# 5.1 申请图形 Buffer

surface::lock 会向 BBQ 申请 buffer,结果保存在一个 ANativeWindow_Buffer 对象中:

typedef struct ANativeWindow_Buffer {
    /// The number of pixels that are shown horizontally.
    int32_t width;

    /// The number of pixels that are shown vertically.
    int32_t height;

    /// The number of *pixels* that a line in the buffer takes in
    /// memory. This may be >= width.
    int32_t stride;

    /// The format of the buffer. One of AHardwareBuffer_Format.
    int32_t format;

    /// The actual bits.
    void* bits;

    /// Do not touch.
    uint32_t reserved[6];
} ANativeWindow_Buffer;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

每个成员都有比较清晰的注释。

Surface::lock 的实现如下:


sp<GraphicBuffer>           mLockedBuffer;
sp<GraphicBuffer>           mPostedBuffer;

// /frameworks/native/libs/gui/Surface.cpp
status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
    
    // ......

    ANativeWindowBuffer* out;
    int fenceFd = -1;
    // 关注点1,向 bbq 申请内存,结果保存在 out 中
    status_t err = dequeueBuffer(&out, &fenceFd);
    ALOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err));
    if (err == NO_ERROR) {
        // 当前帧
        sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
        const Rect bounds(backBuffer->width, backBuffer->height);

        Region newDirtyRegion;
        if (inOutDirtyBounds) {
            newDirtyRegion.set(static_cast<Rect const&>(*inOutDirtyBounds));
            newDirtyRegion.andSelf(bounds);
        } else {
            newDirtyRegion.set(bounds);
        }
        
        // 前一帧
        // frontBuffer 就是前面一帧图像对应的 Buffer
        const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
        const bool canCopyBack = (frontBuffer != nullptr &&
                backBuffer->width  == frontBuffer->width &&
                backBuffer->height == frontBuffer->height &&
                backBuffer->format == frontBuffer->format);

        if (canCopyBack) {
            // 关注点2
            // 一帧大概率只更新部分区域,不会更新整个窗口
            // 这里把不需要更新的区域的数据拷贝到新申请的 buffer 中去
            const Region copyback(mDirtyRegion.subtract(newDirtyRegion));
            if (!copyback.isEmpty()) {
                copyBlt(backBuffer, frontBuffer, copyback, &fenceFd);
            }
        } else {
            // if we can't copy-back anything, modify the user's dirty
            // region to make sure they redraw the whole buffer
            newDirtyRegion.set(bounds);
            mDirtyRegion.clear();
            Mutex::Autolock lock(mMutex);
            for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
                mSlots[i].dirtyRegion.clear();
            }
        }


        { // scope for the lock
            Mutex::Autolock lock(mMutex);
            int backBufferSlot(getSlotFromBufferLocked(backBuffer.get()));
            if (backBufferSlot >= 0) {
                Region& dirtyRegion(mSlots[backBufferSlot].dirtyRegion);
                mDirtyRegion.subtract(dirtyRegion);
                dirtyRegion = newDirtyRegion;
            }
        }

        mDirtyRegion.orSelf(newDirtyRegion);
        if (inOutDirtyBounds) {
            *inOutDirtyBounds = newDirtyRegion.getBounds();
        }

        // mmap 映射到当前进程内存空间
        void* vaddr;
        status_t res = backBuffer->lockAsync(
                GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                newDirtyRegion.bounds(), &vaddr, fenceFd);

        ALOGW_IF(res, "failed locking buffer (handle = %p)",
                backBuffer->handle);

        if (res != 0) {
            err = INVALID_OPERATION;
        } else {
            // 关注点3
            mLockedBuffer = backBuffer;
            outBuffer->width  = backBuffer->width;
            outBuffer->height = backBuffer->height;
            outBuffer->stride = backBuffer->stride;
            outBuffer->format = backBuffer->format;
            outBuffer->bits   = vaddr;
        }
    }
    return err;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
  • 关注点1,向 BBQ 申请一块 buffer,结果保存在 ANativeWindowBuffer* out
  • 关注点2,新的一帧大概率只更新部分区域,不会更新整个窗口,这里把不需要更新的区域的数据拷贝到新申请的 buffer 中去
  • 关注点3,新的 buffer,会保存到 Surface 的 sp<GraphicBuffer> mLockedBuffer 成员中

# 5.2 构建 graphics::Canvas 对象

graphics::Canvas 的构造函数实现如下:

        // /frameworks/base/libs/hwui/apex/include/android/graphics/canvas.h
        ACanvas* mCanvas;

        Canvas(JNIEnv* env, jobject canvasObj) :
                mCanvas(ACanvas_getNativeHandleFromJava(env, canvasObj)),
                mOwnedPtr(false) {}
1
2
3
4
5
6

ACanvas_getNativeHandleFromJava 函数构建一个 ACanvas 对象给到 ACanvas* mCanvas; 成员。

// /frameworks/base/libs/hwui/apex/android_canvas.cpp
ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvasObj) {
    return TypeCast::toACanvas(GraphicsJNI::getNativeCanvas(env, canvasObj));
}

// /frameworks/base/libs/hwui/apex/TypeCast.h
// 指针强转为 ACanvas * 类型
static inline ACanvas* toACanvas(Canvas* canvas) {
     return reinterpret_cast<ACanvas *>(canvas);
}
1
2
3
4
5
6
7
8
9
10

getNativeCanvas 获取到 Java 层传递过来的 android::Canvas* 指针返回

// /frameworks/base/libs/hwui/jni/Graphics.cpp

gCanvas_nativeInstanceID = GetFieldIDOrDie(env, gCanvas_class, "mNativeCanvasWrapper", "J");

android::Canvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) {
    ALOG_ASSERT(env);
    ALOG_ASSERT(canvas);
    ALOG_ASSERT(env->IsInstanceOf(canvas, gCanvas_class));
    jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID);
    if (!canvasHandle) {
        return NULL;
    }
    return reinterpret_cast<android::Canvas*>(canvasHandle);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这里会去获取到 Java 层 Canvas 对象的 mNativeCanvasWrapper 成员,该成员的值是一个 android::Canvas 指针。

那 mNativeCanvasWrapper 是什么时候被赋值的呢?

// /frameworks/base/core/java/android/view/Surface.java
public class Surface implements Parcelable {
    // ......
    private final Canvas mCanvas = new CompatibleCanvas();
    // ......
}
1
2
3
4
5
6

Surface 对应有一个成员 Canvas mCanvas,在定义时被初始化为子类 CompatibleCanvas。

    private final class CompatibleCanvas extends Canvas {
        // A temp matrix to remember what an application obtained via {@link getMatrix}
        private Matrix mOrigMatrix = null;

        @Override
        public void setMatrix(Matrix matrix) {
            if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
                // don't scale the matrix if it's not compatibility mode, or
                // the matrix was obtained from getMatrix.
                super.setMatrix(matrix);
            } else {
                Matrix m = new Matrix(mCompatibleMatrix);
                m.preConcat(matrix);
                super.setMatrix(m);
            }
        }

        @SuppressWarnings("deprecation")
        @Override
        public void getMatrix(Matrix m) {
            super.getMatrix(m);
            if (mOrigMatrix == null) {
                mOrigMatrix = new Matrix();
            }
            mOrigMatrix.set(m);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

这里会调用到 Canvas 的构造函数:

// /frameworks/base/graphics/java/android/graphics/Canvas.java
    public Canvas() {
        if (!isHardwareAccelerated()) {
            // 0 means no native bitmap
            mNativeCanvasWrapper = nInitRaster(0);
            mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
                    this, mNativeCanvasWrapper);
        } else {
            mFinalizer = null;
        }
    }
1
2
3
4
5
6
7
8
9
10
11

mNativeCanvasWrapper 在这里被赋值为 nInitRaster 方法的返回值。

nInitRaster 是一个 native 方法,对应的 JNI 实现如下:

// /frameworks/base/libs/hwui/jni/android_graphics_Canvas.cpp
static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) {
    SkBitmap bitmap;
    if (bitmapHandle != 0) {
        bitmap::toBitmap(bitmapHandle).getSkBitmap(&bitmap);
    }
    return reinterpret_cast<jlong>(Canvas::create_canvas(bitmap));
}

// /frameworks/base/libs/hwui/SkiaCanvas.cpp
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
    return new SkiaCanvas(bitmap);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

可以看到 mNativeCanvasWrapper 实际是一个 SkiaCanvas 类型的指针。

SkiaCanvas 的构造函数实现如下:

// /frameworks/base/libs/hwui/SkiaCanvas.cpp
std::unique_ptr<SkCanvas> mCanvasOwned;  
SkCanvas* mCanvas; 

SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) {
    mCanvasOwned = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap));
    mCanvas = mCanvasOwned.get();
}
1
2
3
4
5
6
7
8

SkiaCanvas 中有一个重要成员 SkCanvas* mCanvas,这个就是 skia 库最终绘制的画布。

类的关系有点繁琐了,我们小结一下:

20241210232357

  • 每一个窗口对应一个 ViewRootImpl 对象
  • 每一个 ViewRootImpl 对象持有一个 Surface mSurface 成员
  • Surface 有两个成员 long mNativeObjectlong mLockedObject;,都指向 native 层的 BBQSurface 对象,BBQSurface 是 Surface 的子类
  • Surfce 有一个 Canvas mCanvas 成员,Canvas 是 BaseCanvas 的子类,BaseCanvas 有一个 long mNativeCanvasWrapper 成员,指向 native 层的 SkiaCanvas 对象,SkiaCanvas 是 android::Canvas 的子类,有一个 SkCanvas* mCanvas; 成员,该成员就是 skia 库中的画布,最终的绘制操作都要通过它实现
  • Native 层会构建一个 android::graphics::Canvas 对象,同时获取到 Java 层 Canvas 中 long mNativeCanvasWrapper 成员指向的 SkiaCanvas 对象,将其强转为一个 ACanvas 指针,并赋值给 android::graphics::CanvasACanvas* mCanvas; 成员。为什么要绕个圈,不让 android::graphics::Canvas 直接持有 android::Canvas 指针?应该是考虑到代码的解耦。当前使用的 SkiaCanvas,未来可能更新为 xxxCanvas。这是一个 CPP 常见的编程技巧。

# 5.3 Buffer 与 graphics::Canvas 对象关联

接着调用 setBuffer 函数,将 buffer 与 graphics::Canvas 对象关联起来。

// /frameworks/base/libs/hwui/apex/include/android/graphics/canvas.h

bool setBuffer(const ANativeWindow_Buffer* buffer,
                       int32_t /*android_dataspace_t*/ dataspace) {
    return ACanvas_setBuffer(mCanvas, buffer, dataspace);
}

// /frameworks/base/libs/hwui/apex/android_canvas.cpp
bool ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
                       int32_t /*android_dataspace_t*/ dataspace) {
    SkBitmap bitmap;
    //使用 buffer 和 dataspace 构造 SkBitmap
    bool isValidBuffer = (buffer == nullptr) ? false : convert(buffer, dataspace, &bitmap);
    // bitmap 设置给 canvas
    TypeCast::toCanvas(canvas)->setBitmap(bitmap);
    return isValidBuffer;
}

// /frameworks/base/libs/hwui/apex/TypeCast.h
static inline Canvas* toCanvas(ACanvas* canvas) {
    return reinterpret_cast<Canvas*>(canvas);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

这里调用 convert,使用 buffer 和 dataspace 来构建一个 SkBitmap 对象。其中 dataspace 是 Surface 的成员,是一个 enum 类型数据,看注释像是用于指定 buffer 的格式。

接着把 SkBitmap 设置给 SkiaCanvas 对象。

convert 的实现如下:

// /frameworks/base/libs/hwui/apex/android_canvas.cpp
static bool convert(const ANativeWindow_Buffer* buffer,
                    int32_t /*android_dataspace_t*/ dataspace,
                    SkBitmap* outBitmap) {
    if (buffer == nullptr) {
        return false;
    }

    // 构建 SkImageInfo
    sk_sp<SkColorSpace> cs(uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace));
    SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
    size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();

    
    // 构建 SkSurface
    sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes);

    // 相关信息配置给 SkBitmap
    if (surface.get() != nullptr) {
        if (outBitmap) {
            outBitmap->setInfo(imageInfo, rowBytes);
            outBitmap->setPixels(buffer->bits);
        }
        return true;
    }
    return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

整个过程如下图所示:

20241211151723

接着使用 SkBitmap 对象初始化一个 SkCanvas,并赋值给 SkiaCanvas 的 mCanvas 成员。

// /frameworks/base/libs/hwui/SkiaCanvas.cpp
void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
    // deletes the previously owned canvas (if any)
    mCanvasOwned.reset(new SkCanvas(bitmap));
    mCanvas = mCanvasOwned.get();

    // clean up the old save stack
    mSaveStack.reset(nullptr);
}
1
2
3
4
5
6
7
8
9

SkCanvas 的构造函数实现如下:

// /external/skia/src/core/SkCanvas.cpp
SkCanvas::SkCanvas(const SkBitmap& bitmap) : SkCanvas(bitmap, nullptr, nullptr, nullptr) {}

SkCanvas::SkCanvas(const SkBitmap& bitmap,
                   std::unique_ptr<SkRasterHandleAllocator> alloc,
                   SkRasterHandleAllocator::Handle hndl,
                   const SkSurfaceProps* props)
        : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
        , fProps(SkSurfacePropsCopyOrDefault(props))
        , fAllocator(std::move(alloc)) {
    inc_canvas();
    
    // 构建一个 SkBitmapDevice 对象
    sk_sp<SkBaseDevice> device(new SkBitmapDevice(bitmap, fProps, hndl));
    // 将 SkBitmapDevice 对象配置给 SkCanvas
    this->init(device);
}

void SkCanvas::init(sk_sp<SkBaseDevice> device) {
    // SkCanvas.h declares internal storage for the hidden struct MCRec, and this
    // assert ensure it's sufficient. <= is used because the struct has pointer fields, so the
    // declared size is an upper bound across architectures. When the size is smaller, more stack
    static_assert(sizeof(MCRec) <= kMCRecSize);

    if (!device) {
        device = sk_make_sp<SkNoPixelsDevice>(SkIRect::MakeEmpty(), fProps);
    }

    // From this point on, SkCanvas will always have a device
    SkASSERT(device);

    fSaveCount = 1;
    fMCRec = new (fMCStack.push_back()) MCRec(device.get());

    // The root device and the canvas should always have the same pixel geometry
    SkASSERT(fProps.pixelGeometry() == device->surfaceProps().pixelGeometry());

    fSurfaceBase = nullptr;
    fBaseDevice = std::move(device);
    fScratchGlyphRunBuilder = std::make_unique<sktext::GlyphRunBuilder>();
    fQuickRejectBounds = this->computeDeviceClipBounds();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// /external/skia/src/core/SkBitmapDevice.cpp
SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps,
                               SkRasterHandleAllocator::Handle hndl)
        : INHERITED(bitmap.info(), surfaceProps)
        , fBitmap(bitmap)
        , fRasterHandle(hndl)
        , fRCStack(bitmap.width(), bitmap.height())
        , fGlyphPainter(this->surfaceProps(), bitmap.colorType(), bitmap.colorSpace()) {
    SkASSERT(valid_for_bitmap_device(bitmap.info(), nullptr));
}
1
2
3
4
5
6
7
8
9
10

整体的结构如下图所示:

20241211153643

# 6. View 的 draw 过程分析

后续就是遍历整个 View 树,每个 View 调用自己的 onDraw 方法,完成绘制。

在 draw 方法中,通常使用 Java 层 Canvas 类的相关 API 进行绘制。

举个例子:

    class TestView(con:Context) : View(con)  {
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            // 调用 Canvas 的 api 绘制
            
            Paint mPaint1 = new Paint();
            Paint mPaint2 = new Paint();

            mPaint1.setStrokeWidth(0);//设置画笔宽度0  ,单位px  默认一个像素
            mPaint1.setColor(Color.BLUE);
            mPaint1.setStyle(Paint.Style.STROKE);
            // .......

            // 画一个矩形(蓝色)
            canvas.drawRect(100, 100, 150, 150, mPaint1);

            // 将画布的原点移动到(400,500)
            canvas.translate(400,500);

            // 再画一个矩形(红色)
            canvas.drawRect(100, 100, 150, 150, mPaint2);

            //......
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Paint 的初始化:

// /frameworks/base/graphics/java/android/graphics/Paint.java
    private long mNativePaint;

    public Paint() {
        this(ANTI_ALIAS_FLAG);
    }

    public Paint(int flags) {
        mNativePaint = nInit();
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
        setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);
        // TODO: Turning off hinting has undesirable side effects, we need to
        //       revisit hinting once we add support for subpixel positioning
        // setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV
        //        ? HINTING_OFF : HINTING_ON);
        mCompatScaling = mInvCompatScaling = 1;
        setTextLocales(LocaleList.getAdjustedDefault());
        mColor = Color.pack(Color.BLACK);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

对应的 JNI 实现:

// /frameworks/base/libs/hwui/jni/Paint.cpp
    static jlong init(JNIEnv* env, jobject) {
        return reinterpret_cast<jlong>(new Paint);
    }
1
2
3
4
// /frameworks/base/libs/hwui/hwui/PaintImpl.cpp
Paint::Paint()
        : SkPaint()
        , mLetterSpacing(0)
        , mWordSpacing(0)
        , mFontFeatureSettings()
        , mMinikinLocaleListId(0)
        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
    // SkPaint::antialiasing defaults to false, but
    // SkFont::edging defaults to kAntiAlias. To keep them
    // insync, we manually set the font to kAilas.
    mFont.setEdging(SkFont::Edging::kAlias);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

Paint 是 SkPaint 的子类。

以 setColor 为例看 Paint 的配置

    // /frameworks/base/graphics/java/android/graphics/Paint.java
    public void setColor(@ColorLong long color) {
        ColorSpace cs = Color.colorSpace(color);

        nSetColor(mNativePaint, cs.getNativeInstance(), color);
        mColor = color;
    }

    private static native void nSetColor(long paintPtr, long colorSpaceHandle,
            @ColorLong long color);
1
2
3
4
5
6
7
8
9
10

对应的 JNI 实现:

    static void setColor(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint color) {
        reinterpret_cast<Paint*>(paintHandle)->setColor(color);
    }
1
2
3

最终调用到 native 层 Paint 的 setColor。

Canvas 绘制以 drawRect 为例进行分析:

实际调用 Canvas 的 API 去 draw:

// /frameworks/base/graphics/java/android/graphics/Canvas.java
    public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
        super.drawRect(left, top, right, bottom, paint);
    }
1
2
3
4
// /frameworks/base/graphics/java/android/graphics/BaseCanvas.java
    public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
        throwIfHasHwFeaturesInSwMode(paint);
        nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
    }

// /frameworks/base/graphics/java/android/graphics/BaseCanvas.java
    private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
            float bottom, long nativePaint);    
1
2
3
4
5
6
7
8
9
// /frameworks/base/libs/hwui/jni/android_graphics_Canvas.cpp
static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
                     jfloat right, jfloat bottom, jlong paintHandle) {
    const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
    get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint);
}

static Canvas* get_canvas(jlong canvasHandle) {
    return reinterpret_cast<Canvas*>(canvasHandle);
}
1
2
3
4
5
6
7
8
9
10
// /frameworks/base/libs/hwui/SkiaCanvas.cpp
void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const Paint& paint) {
    if (CC_UNLIKELY(paint.nothingToDraw())) return;
    // 扔到事务队列中去
    applyLooper(&paint, [&](const SkPaint& p) {
        // 调用到 skCanvas
        // 最终都会通过 SKBitmap 写入到 buffer 中去
        mCanvas->drawRect({left, top, right, bottom}, p);
    });
}
1
2
3
4
5
6
7
8
9
10

实际就是调用到 skia 库中的 API。

# 7. Canvas 的提交过程分析

完成绘制,就可以将 buffer 提交给 BBQ:

// /frameworks/base/core/java/android/view/Surface.java
    public void unlockCanvasAndPost(Canvas canvas) {
        synchronized (mLock) {
            checkNotReleasedLocked();

            if (mHwuiContext != null) { // 硬件绘制走这里
                mHwuiContext.unlockAndPost(canvas);
            } else { // 软件绘制走这里
                unlockSwCanvasAndPost(canvas);
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
// /frameworks/base/core/java/android/view/Surface.java
    private void unlockSwCanvasAndPost(Canvas canvas) {
        if (canvas != mCanvas) {
            throw new IllegalArgumentException("canvas object must be the same instance that "
                    + "was previously returned by lockCanvas");
        }
        if (mNativeObject != mLockedObject) {
            Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
                    Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
                    Long.toHexString(mLockedObject) +")");
        }
        if (mLockedObject == 0) {
            throw new IllegalStateException("Surface was not locked");
        }
        try {
            nativeUnlockCanvasAndPost(mLockedObject, canvas);
        } finally {
            nativeRelease(mLockedObject);
            mLockedObject = 0;
        }
    }

    private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
    
    // 完成一些回收操作
    private static native void nativeRelease(long nativeObject);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

对应的 JNI 实现:

// /frameworks/base/core/jni/android_view_Surface.cpp
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    if (!isSurfaceValid(surface)) {
        return;
    }

    // detach the canvas from the surface
    graphics::Canvas canvas(env, canvasObj);
    // 解除 canvas 与 buffer 的关联
    canvas.setBuffer(nullptr, ADATASPACE_UNKNOWN);

    // 关注点
    // unlock surface
    status_t err = surface->unlockAndPost();
    if (err < 0) {
        jniThrowException(env, IllegalArgumentException, NULL);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
status_t Surface::unlockAndPost()
{
    if (mLockedBuffer == nullptr) {
        ALOGE("Surface::unlockAndPost failed, no locked buffer");
        return INVALID_OPERATION;
    }

    int fd = -1;
    status_t err = mLockedBuffer->unlockAsync(&fd);
    ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);

    // 提交 buffer 到 BBQ
    err = F(mLockedBuffer.get(), fd);
    ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",
            mLockedBuffer->handle, strerror(-err));

    // 当前帧变为上一帧
    mPostedBuffer = mLockedBuffer;
    mLockedBuffer = nullptr;
    return err;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 参考资料与推荐阅读